<?php

namespace App\Core;

use App\Core\Exception\DefaultException;
use App\Util\ArrayHelper;
use App\Util\CommonHelper;
use App\Util\UserIdentityHelper;
use ArrayObject;
use EasySwoole\Component\Di;
use EasySwoole\Mysqli\QueryBuilder;
use EasySwoole\ORM\AbstractModel;
use EasySwoole\ORM\DbManager;
use EasySwoole\ORM\Exception\Exception;
use EasySwoole\Redis\Redis;

class BaseModel extends AbstractModel
{
    use OutputTrait;

    // 都是非必选的，默认值看文档下面说明
    protected $autoTimeStamp = 'datetime';
    protected $createTime = 'created_at';
    protected $updateTime = 'updated_at';

    const ENABLED = 1; //显示
    const DISABLED = 0; //不显示
    /**@var $CACHE_EXPIRE 默认缓存时间 单位：秒*/
    const CACHE_EXPIRE = 600;//默认缓存时间 单位：秒
    const OPERATION_TYPE_CREATE = 1;  //添加
    const OPERATION_TYPE_UPDATE = 2;  //更新
    const OPERATION_TYPE_DELETE = 3;  //删除

    const STATE_SUCCESS = 1;//状态成功
    const STATE_FAILED = 0;     //状态失败

    const INSERT_INTO = 1;
    const INSERT_IGNORE = 2;
    const INSERT_ORUPDATE = 3;
    //const INSERT_REPLACE = 4;
    /**
     * 获取索引数组指定值
     * @param array $array
     * @param string|null $key
     * @param string|null $default
     * @return array|mixed|null
     * @throws DefaultException
     * @throws \Throwable
     */
    protected static function getMap(array $array, ?string $key = null, ?string $default = null)
    {
        if ($key === null) {
            return $array;
        }

        return ArrayHelper::getValue($array, $key, $default);
    }

    /**
     * 删除缓存 delCache
     * @return void
     * @throws \Throwable
     */
    public static function delCache($key){
        /** @var Redis $cache */
        $cache = Di::getInstance()->get("REDIS");
        //缓存键名
        $cache_key = $key . '*';
        //获取存在的key
        $keys = $cache->keys($cache_key);
//        var_dump($keys);
        //清空缓存
        if ( !empty($keys) ){
            foreach ($keys as $key) {
                $cache->del($key);
            }
        }
    }

    /**
     * 根据条件读取一行记录【自动补上is_delete=0】值
     * @param array $map
     * @param false $isArray
     * @param array $field
     * @param array $with
     * @return static|array|bool|AbstractModel|\EasySwoole\ORM\Db\Cursor|\EasySwoole\ORM\Db\CursorInterface|null
     * @throws \EasySwoole\Mysqli\Exception\Exception
     * @throws \EasySwoole\ORM\Exception\Exception
     * @throws \Throwable
     */
    public static function getOne(array $map, bool $isArray = false, array $field = [], array $with = [], bool $write = false)
    {
        if (!isset($map['is_delete'])) {
            $map['is_delete'] = 0;
        }
        $modelObj = self::create();
        if ($write) {
            $modelObj = self::create()->connection('write');
        }
        if ($with) {
            $modelObj->with($with);
        }
        if (!empty($field)) {
            $modelObj->field($field);
        }
        if (isset(static::$mysqlConnectionName) && static::$mysqlConnectionName) {
            $modelObj->connection(static::$mysqlConnectionName);
        }
        $result = $modelObj->limit(1)->get($map);
        if ($isArray) {
            if ($result) {
                return $result->toArray();
            } else {
                return false;
            }
        } else {
            return $result;
        }
    }

    /**
     * 根据条件读取数量
     * @param array $map
     * @return static|array|bool|AbstractModel|\EasySwoole\ORM\Db\Cursor|\EasySwoole\ORM\Db\CursorInterface|null
     * @throws \EasySwoole\Mysqli\Exception\Exception
     * @throws \EasySwoole\ORM\Exception\Exception
     * @throws \Throwable
     */
    public static function getCount(array $map)
    {
        if (!isset($map['is_delete'])) {
            $map['is_delete'] = 0;
        }

        $modelObj = self::create();

        $result = $modelObj->where($map)->count();

        return $result;
    }

    /**
     * 批量插入
     * @param array $data
     * @return \EasySwoole\ORM\Db\Result
     * @throws Exception
     * @throws \Throwable
     */
    public static function insertMulti(array $data, $type = self::INSERT_INTO, $orUpdate = [], $tableName = null)
    {

        $current = CommonHelper::currentTime();
        $columns = $values = [];

        $username = UserIdentityHelper::getUsername();

        foreach ($data as &$v) {
            //追加字段
            if (!empty($username)) $v['created_by'] = $v['updated_by'] = $username;
            isset($v['created_at']) or $v['created_at'] = $current;

            ksort($v);
            empty($columns) && $columns = array_keys($v);

            $vs = [];
            foreach ($v as $t) {
                $vs[] = CommonHelper::quoteValue($t);
            }
            $values[] = '(' . implode(', ', $vs) . ')';
        }
        if($tableName === null) {
            $tableName = self::create()->connection('write')->getTableName();
        }
        $queryBuild = new QueryBuilder();
        if ($type == self::INSERT_IGNORE) {
            $stringSql = 'INSERT IGNORE INTO ' . $tableName
                . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
        } else if ($type == self::INSERT_ORUPDATE && $orUpdate) {
            $orUpdateStr = '';
            foreach ($orUpdate as $k => $v) {
                if (is_array($v) && is_string($v[0])) {
                    $orUpdateStr .= $k . '=' . trim(CommonHelper::quoteValue($v['0']), "'") . ',';
                } else {
                    $orUpdateStr .= $k . '=' . CommonHelper::quoteValue($v) . ',';
                }
            }
            $stringSql = 'INSERT INTO ' . $tableName
                . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values) . ' ON DUPLICATE KEY UPDATE ' . rtrim($orUpdateStr, ',');
        } else {
            $stringSql = 'INSERT INTO ' . $tableName
                . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
        }
//        var_dump($stringSql);
        $queryBuild->raw($stringSql);
        return DbManager::getInstance()->query($queryBuild, true, 'write');
    }

    /**
     * 刪除数据
     * @param array $map
     * @return mixed
     * @throws DefaultException
     * @throws \EasySwoole\ORM\Exception\Exception
     * @throws \Throwable
     */
    public static function deleteRecord(array $map)
    {
        if (!isset($map['is_delete'])) {
            $map['is_delete'] = 0;
        }

        $modelObj = self::create()->connection('write');

        $username = UserIdentityHelper::getUsername();

        $updata = [
            'is_delete'  => 1,
            'deleted_at' => CommonHelper::currentTime(),
            'updated_by' => $username,
        ];

        $modelObj->update($updata, $map);
        self::operationLog($modelObj->lastQueryResult()->getLastInsertId(), $updata, [], self::OPERATION_TYPE_DELETE, self::STATE_SUCCESS);
        return $modelObj->lastQueryResult()->getAffectedRows();
    }


    /**
     * 创建用于查询列表的模型对象
     * @param array $where AND条件
     * @param array $orWhere 或条件，必须为一维数组，以字符串方式，需要注意防注入
     * <code>
     *  [
     *      "name like '%xxx%' or status=1",
     *      "gender = 1 or type = 2",
     *  ]
     * </code>
     * @param array $pagination 分页信息，没有page_size索引时表示不分页，拿全部数据
     * <code>
     *  [
     *      'page'=>1,
     *      'page_size'=>20
     *  ]
     * </code>
     * @param bool $withCount 是否查询数量
     * @return AbstractModel
     * @throws \EasySwoole\ORM\Exception\Exception
     */
    public static function makeQueryModel(?array $where = [], ?array $orWhere = [], ?array $pagination = [], string $alias = '', bool $write = false): AbstractModel
    {
        $modelObj = self::create();
        if (isset(static::$mysqlConnectionName) && static::$mysqlConnectionName) {
            $modelObj->connection(static::$mysqlConnectionName);
        }
        if (!empty($alias)) {
            $modelObj->alias($alias);
        }
        if ($write) {
            $modelObj = self::create()->connection('write');
        }

        if (!isset($where['is_delete'])) {
            $delField = empty($alias) ? 'is_delete' : $alias . '.is_delete';
            $where[$delField] = 0;
        }

        $modelObj->where($where);
        if ($orWhere) {
            foreach ($orWhere as $v) {
                $modelObj->where($v);
            }
        }

        if (isset($pagination['page_size'])) {
            //有分布数据时返回总数
            $modelObj->withTotalCount();

            $pageSize = $pagination['page_size'];
            $page = $pagination['page'] ?? 1;

            $page > 0 || $page = 1;
            $pageSize > 0 || $pageSize = 20;
            $offset = ($page - 1) * $pageSize;
            $modelObj->limit($offset, $pageSize);
        }

        return $modelObj;
    }

    /**
     * 保存 插入 自动追加固定字段 insertData
     * @param array $data
     * @return BaseModel|AbstractModel
     * @throws Exception
     * @throws \Throwable
     */
    public static function insertData(array $data)
    {
        if (!isset($data['created_by']) || empty($data['created_by'])) {
            $username = UserIdentityHelper::getUsername();
            $data['created_by'] = $username ?: '';
        }

        if (!isset($data['updated_by']) || empty($data['updated_by'])) {
            $username = UserIdentityHelper::getUsername();
            $data['updated_by'] = $username ?: '';
        }
        try {
            $selfModel = self::create()->connection('write');
            $selfModel->data($data)->save();
        } catch (\Throwable $e) {
            self::operationLog(0, $data, [], self::OPERATION_TYPE_CREATE, self::STATE_FAILED, '添加数据异常');
            throw $e;
        }
        self::operationLog($selfModel->lastQueryResult()->getLastInsertId(), $data, [], self::OPERATION_TYPE_CREATE, self::STATE_SUCCESS);
        return $selfModel;
    }

    /**
     * 更新 自动追加固定字段
     * @param array $data
     * @param null $where
     * @param bool $allowUpdateWithNoCondition 是否允许无条件更新
     * @return BaseModel|AbstractModel
     * @throws Exception
     * @throws \EasySwoole\Mysqli\Exception\Exception
     * @throws \Throwable
     */
    public static function updateData(array $data = [], $where = null, $allowUpdateWithNoCondition = false)
    {

        if (!isset($data['updated_by']) || empty($data['updated_by'])) {
            $username = UserIdentityHelper::getUsername();
            $data['updated_by'] = $username ?: '';
        }
        $one = self::getOne($where, true);
        try {
            $selfModel = self::create()->connection('write');
            $selfModel->where($where)->where(['is_delete' => self::DISABLED])->update($data, [], $allowUpdateWithNoCondition);
        } catch (\Throwable $e) {
            if ($one) {
                self::operationLog($one['id'], $data, $one, self::OPERATION_TYPE_UPDATE, self::STATE_FAILED, '修改数据异常');
            }
            throw $e;
        }
        if ($one) {
            self::operationLog($one['id'], $data, $one, self::OPERATION_TYPE_UPDATE, self::STATE_SUCCESS);
        }

        return $selfModel;
    }

    /**
     * 批量插入 - 存在则更新
     * @param array $data
     * @return \EasySwoole\ORM\Db\Result
     * @throws Exception
     * @throws \Throwable
     */
    public static function duplicateData(array $data)
    {

        //数据表
        $tableName = self::create()->connection('write')->getTableName();

        //主键
        $pk = self::create()->connection('write')->schemaInfo()->getPkFiledName();

        $current = CommonHelper::currentTime();
        $columns = $values = $duplicate = [];

        $username = UserIdentityHelper::getUsername();

        foreach ($data as &$v) {
            //追加字段
            if (!empty($username)) $v['created_by'] = $v['updated_by'] = $username;
            isset($v['created_at']) or $v['created_at'] = $current;
            isset($v['updated_at']) or $v['updated_at'] = $current;

            ksort($v);
            empty($columns) && $columns = array_keys($v);

            $vs = [];
            foreach ($v as $t) {
                $vs[] = CommonHelper::quoteValue($t);
            }
            $values[] = '(' . implode(', ', $vs) . ')';
        }

        //主键存在需要更新的字段
        foreach ($columns as $column) {
            if ($column != $pk) {
                $duplicate[] = "$column = VALUES($column)";
            }
        }

        $queryBuild = new QueryBuilder();

        $stringSql = 'INSERT INTO ' . $tableName
            . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values)
            . ' ON DUPLICATE KEY UPDATE ' . implode(',', $duplicate);

        $queryBuild->raw($stringSql);
        return DbManager::getInstance()->query($queryBuild, true, 'write');
    }
    protected static function operationLog($record_id, $afterData, $beforeData = [], $operationType = '', $state = 1, $remark = '')
    {
        if (!isset(static::$logTableName) || !static::$logTableName) {
            return;
        }
        if ($beforeData) {
            $beforeData = array_intersect_key($beforeData, $afterData);
        } else {
            $beforeData = new \ArrayObject();
        }
        $afterDataJson = json_encode($afterData, JSON_UNESCAPED_UNICODE);
        $beforeDataJson = json_encode($beforeData, JSON_UNESCAPED_UNICODE);
        $operatorId = UserIdentityHelper::getId() ?: 0;
        $logDate = date('Y-m-d H:i:s');
        $createdAt = $logDate;
        $updatedAt = $logDate;
        $createdBy = UserIdentityHelper::getUsername();
        $updatedBy = $createdBy;
        $queryBuild = new QueryBuilder();
        $sql = "INSERT INTO " . static::$logTableName . " 
                (record_id, log_date, operator_id, operation_type, before_data, after_data, state, remark, created_at, created_by, updated_at, updated_by)
                VALUE (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

        $queryBuild->raw($sql, [
            $record_id,
            $logDate,
            $operatorId,
            $operationType,
            $beforeDataJson,
            $afterDataJson,
            $state,
            $remark,
            $createdAt,
            $createdBy,
            $updatedAt,
            $updatedBy
        ]);
        DbManager::getInstance()->query($queryBuild, true, 'write');
    }
}
